﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace TaskKernel.Domain
{
    public class Task : AggregateRoot
    {
        protected IList<File> files;
        protected IList<AbstractTaskHistoryEntry> historyEntries;
        protected IList<Participant> participantsWithOwner;
        protected IList<Rating> ratings;
        protected IList<Reader> readers;
        protected IList<Reminder> reminders;
        protected IList<WorkingPeriod> workingPeriods;

        protected Task()
        {
            historyEntries = new List<AbstractTaskHistoryEntry>();
            files = new List<File>();
            readers = new List<Reader>();
            reminders = new List<Reminder>();
            participantsWithOwner = new List<Participant>();
            workingPeriods = new List<WorkingPeriod>();
            ratings = new List<Rating>();
        }

        public Task(Account account, Department department, String taskname)
            : this()
        {
            Department = department;
            Owner = account;
            DateCreate = DateTime.Now;
            TaskName = taskname;
            SetTaskInProgress();
            ViewTask(account);
        }

        public virtual Account Executor { get; protected set; }
        public virtual Account Owner { get; protected set; }
        public virtual Account ClosedBy { get; protected set; }
        public virtual Account AcceptClosedBy { get; protected set; }
        public virtual Department Department { get; protected set; }
        public virtual DateTime DateCreate { get; protected set; }
        public virtual DateTime TimeCreate { get; protected set; }
        public virtual DateTime? DateClose { get; protected set; }
        public virtual DateTime? DateExpiration { get; protected set; }
        public virtual DateTime? DateLastHistoryEntry { get; protected set; }
        public virtual TaskPriority Priority { get; protected set; }
        public virtual TaskStatus Status { get; protected set; }
        public virtual string TaskName { get; protected set; }
        public virtual string Tag { get; protected set; }
        public virtual int PercentComplete { get; protected set; }
        public virtual int Code { get; protected set; }

        public virtual IEnumerable<Participant> ParticipantsWithOwner
        {
            get { return participantsWithOwner; }
        }

        public virtual IEnumerable<Account> Participants
        {
            get { return participantsWithOwner.Select(x => x.Login); }
        }

        public virtual IEnumerable<WorkingPeriod> WorkingPeriods
        {
            get { return workingPeriods; }
        }

        public virtual IEnumerable<Rating> Ratings
        {
            get { return ratings; }
        }

        public virtual IEnumerable<File> Files
        {
            get { return files; }
        }

        public virtual IEnumerable<Reader> Readers
        {
            get { return readers; }
        }

        public virtual IEnumerable<Reminder> Reminders
        {
            get { return reminders; }
        }

        public virtual IEnumerable<AbstractTaskHistoryEntry> HistoryEntries
        {
            get { return historyEntries; }
        }

        public virtual bool HasParticipant(Account account)
        {
            try{
                return participantsWithOwner.Any(p => p.Login.UID == account.UID);
            }
            catch{
                throw new NullReferenceException("Задача: " + UID + ". Поле Login в участниках пустое.");
            }
        }

        public virtual bool ForceClose(Account account)
        {
            if (!IsClosed()){
                if (null != ClosedBy){
                    if (ClosedBy.Equals(account))
                        return false;
                }
                SetPercentComplete(account, 100);
                VisibleSystemMessage(account, Tools.RedColorString("Задача закрыта"));
                CleanAllReaders();
                ClosedBy = account;
                SetCloseDate(DateTime.Now);
                SetStatus(TaskStatus.Closed);
                return true;
            }
            return false;
        }

        public virtual bool HasPartialWorkingPeriodFor(Account account)
        {
            return workingPeriods.Any(x => x.Owner == account && x.StopTime == null);
        }

        public virtual bool StartWorkingPeriodTrigger(Account account)
        {
            if (HasPartialWorkingPeriodFor(account)){
                var workingPeriod = workingPeriods.Where(x => x.Owner == account).SingleOrDefault(x => x.StopTime == null);
                if (workingPeriod != null)
                    workingPeriod.End();
                return false;
            }
            workingPeriods.Add(new WorkingPeriod(this, account));
            return true;
        }

        public virtual bool Close(Account account)
        {
            if (!IsClosed() && Owner.Equals(account)){
                if (null != ClosedBy){
                    if (ClosedBy.Equals(account))
                        return false;
                }
                SetPercentComplete(account, 100);
                VisibleSystemMessage(account, Tools.RedColorString("Задача закрыта"));
                CleanAllReaders();
                ClosedBy = account;
                SetCloseDate(DateTime.Now);
                SetStatus(TaskStatus.Closed);
                return true;
            }
            return false;
        }

        public virtual bool RequestClose(Account account)
        {
            if (!IsClosed()){
                if (null != AcceptClosedBy){
                    if (AcceptClosedBy.Equals(account))
                        return false;
                }
                SetPercentComplete(account, 99);
                VisibleSystemMessage(account, Tools.RedColorString("Подан запрос на закрытие задачи"));
                CleanAllReadersWithout(account);
                AcceptClosedBy = account;
                SetStatus(TaskStatus.WaitingToClose);
                return true;
            }
            return false;
        }

        public virtual bool UnClose(Account logonAccount)
        {
            if (IsClosed()){
                SetPercentComplete(logonAccount, 0);
                VisibleSystemMessage(logonAccount, Tools.RedColorString("Задача возвращена в работу"));
                CleanAllReaders();
                ClosedBy = null;
                AcceptClosedBy = null;
                DateClose = null;
                SetStatus(TaskStatus.InProgress);
                Executor = null;
                return true;
            }
            return false;
        }

        public virtual Reminder GetReminderFor(Account account)
        {
            return reminders.Where(x => x.Owner.UID == account.UID).SingleOrDefault();
        }

        public virtual bool SetExpirationDate(DateTime? expirDate, Account logonAccount)
        {
            if (DateExpiration != expirDate){
                DateExpiration = expirDate;
                if (DateExpiration.HasValue){
                    VisibleSystemMessage(logonAccount, Tools.RedColorString("Срок исполнения: " +
                                                                            String.Format("{0:dd}/{0:MM}/{0:yyyy}", expirDate)));
                }
                CleanAllReadersWithout(logonAccount);
                SetTaskInProgress();
                return true;
            }
            return false;
        }

        public virtual bool SetDepartment(Account logonAccount, Department department)
        {
            if (Department != department){
                Department = department;
                InvisibleSystemMessage(logonAccount, Tools.RedColorString("Задача передана на исполнение в раздел: " +
                                                                          department.DepartmentName));
                RemoveExecutor();
                CleanAllReadersWithout(logonAccount);
                SetTaskInProgress();
                return true;
            }
            return false;
        }

        public virtual bool SetCloseDate(DateTime dateClose)
        {
            if (DateClose != dateClose){
                DateClose = dateClose;
                return true;
            }
            return false;
        }

        public virtual bool AddHistoryEntry(Account account, String comment)
        {
            var count = historyEntries.Count;
            historyEntries.Add(new CommentTaskHistoryEntry(this, account, comment, HistoryEntryType.Comment));
            count++;
            DateLastHistoryEntry = DateTime.Now;
            if (count == historyEntries.Count){
                CleanAllReadersWithout(account);
                SetTaskInProgress();
                ApplyChange(new SystemEvent(account, comment));
                return true;
            }
            return false;
        }

        public virtual void SetTaskInProgress()
        {
            if (IsRequestClose()) SetStatus(TaskStatus.InProgress);
            if (PercentComplete == 100) PercentComplete = 99;
            AcceptClosedBy = null;
            ClosedBy = null;
        }

        public virtual bool SetExecutor(Account executor, Account logonAccount)
        {
            if (null != Executor && Executor.Equals(executor))
                return false;
            if (null == Executor && null == executor)
                return false;
            Executor = executor;
            CleanAllReadersWithout(logonAccount);
            SetTaskInProgress();
            VisibleSystemMessage(logonAccount, Tools.RedColorString("Исполнитель задачи: " + (Executor != null ? executor.FullName : "Не установлен")));
            return true;
        }

        public virtual bool IsExpired()
        {
            const int IdenticalCondition = 0;
            if (null != DateExpiration)
                if (DateTime.Compare((DateTime) DateExpiration, DateTime.Now) < IdenticalCondition) return true;
            return false;
        }

        public virtual bool IsReadBy(Account account)
        {
            return Readers.Any(x => x.Login.UID == account.UID);
        }

        public virtual bool IsParticipant(Account account)
        {
            return ParticipantsWithOwner.Any(x => x.Login.UID == account.UID);
        }

        public virtual bool HasExecutor()
        {
            return Executor != null;
        }

        public virtual bool IsNew()
        {
            return Status == TaskStatus.InProgress && !HasExecutor();
        }

        public virtual bool InProgress()
        {
            return Status == TaskStatus.InProgress && HasExecutor();
        }

        public virtual bool IsClosed()
        {
            return Status == TaskStatus.Closed;
        }

        public virtual bool IsRequestClose()
        {
            return Status == TaskStatus.WaitingToClose;
        }

        public virtual bool ViewTask(Account account)
        {
            if (!IsReadBy(account)){
                readers.Add(new Reader(this, account));
                return true;
            }
            return false;
        }

        public virtual void CleanAllReaders()
        {
            readers.Clear();
        }

        public virtual void CleanAllReadersWithout(Account account)
        {
            CleanAllReaders();
            ViewTask(account);
        }

        public virtual bool SetPriority(TaskPriority priority)
        {
            if (!Priority.Equals(priority)){
                Priority = priority;
                SetTaskInProgress();
                return true;
            }
            return false;
        }

        public virtual void SetStatus(TaskStatus status)
        {
            Status = status;
        }

        public virtual bool SetPercentComplete(Account logonAccount, int percentComplete)
        {
            if (PercentComplete != percentComplete){
                PercentComplete = percentComplete;
                InvisibleSystemMessage(logonAccount,
                                       Tools.RedColorString("Задача выполнена на " + percentComplete + "%"));
                CleanAllReadersWithout(logonAccount);
                SetTaskInProgress();
                return true;
            }
            return false;
        }

        private void VisibleSystemMessage(Account account, String comment)
        {
            historyEntries.Add(new CommentTaskHistoryEntry(this, account, comment, HistoryEntryType.Comment));
            DateLastHistoryEntry = DateTime.Now;
            ApplyChange(new SystemEvent(account, comment));
        }

        private void InvisibleSystemMessage(Account account, String comment)
        {
            historyEntries.Add(new InvisibleSystemMessage(this, account, comment, HistoryEntryType.InvisibleSystemMessage));
            DateLastHistoryEntry = DateTime.Now;
            ApplyChange(new SystemEvent(account, comment));
        }

        public virtual bool AddHistoryEntry(AbstractTaskHistoryEntry historyEntry)
        {
            var count = historyEntries.Count;
            historyEntries.Add(historyEntry);
            count++;
            DateLastHistoryEntry = DateTime.Now;
            return count == historyEntries.Count;
        }

        //public virtual bool AddFile(Account account, String filename, Byte[] content)
        //{
        //    var file = new File(filename, content);
        //    return AddFile(account, file);
        //}
        public virtual bool AddFile(Account account, File file)
        {
            var count = files.Count;
            file.SetOwner(account);
            file.SetTask(this);
            files.Add(file);
            historyEntries.Add(new FileTaskHistoryEntry(this, file, HistoryEntryType.File));
            count++;
            DateLastHistoryEntry = DateTime.Now;
            if (count == files.Count){
                CleanAllReadersWithout(account);
                SetTaskInProgress();
                ApplyChange(new SystemEvent(account, file.FileName));
                return true;
            }
            return false;
        }

        public virtual bool AddParticipants(Account logonAccount, IEnumerable<Account> participants)
        {
            if (participants.Any(participant => !AddParticipant(logonAccount, participant)))
                return false;
            InvisibleSystemMessage(logonAccount, Tools.RedColorString("Добавлены: " + participants.StringFromList()));
            CleanAllReadersWithout(logonAccount);
            SetTaskInProgress();
            return true;
        }

        public virtual bool AddParticipant(Account account, Account participant)
        {
            if (!participantsWithOwner.Any(x => x.Login.UID == participant.UID)){
                participantsWithOwner.Add(new Participant(this, account, participant));
                return true;
            }
            return false;
        }

        public virtual bool DeleteParticipants(Account logonAccount, IEnumerable<Account> participants)
        {
            if (participants.Any(account => !DeleteParticipant(logonAccount, account)))
                return false;
            CleanAllReadersWithout(logonAccount);
            SetTaskInProgress();
            InvisibleSystemMessage(logonAccount, Tools.RedColorString("Удалены: " + participants.StringFromList()));
            return true;
        }

        public virtual bool DeleteParticipant(Account account, Account participant)
        {
            if (participantsWithOwner.Remove(new Participant(this, account, participant)))
                return true;
            return false;
        }

        public virtual bool HasReminderFor(Account account)
        {
            return Reminders.Any(x => x.Owner.UID == account.UID);
        }

        public virtual bool AddReminder(Account account, DateTime datetime, string note)
        {
            var reminder = new Reminder(this, account, datetime, note);
            if (HasReminderFor(account)){
                if (!reminders.Contains(reminder)){
                    if (DeleteReminder(account)){
                        reminders.Add(reminder);
                        return true;
                    }
                }
                return false;
            }
            reminders.Add(reminder);
            return true;
        }

        public virtual bool DeleteReminder(Account account)
        {
            return reminders.Remove(reminders.SingleOrDefault(x => x.Owner.Equals(account)));
        }

        public virtual IEnumerable<Reminder> GetAllExpirationReminders()
        {
            return Reminders.Where(x => x.DateTime <= DateTime.Now);
        }

        public virtual IEnumerable<Reminder> GetExpirationRemindersByTime(DateTime dateTime)
        {
            return
                Reminders.Where(p => p.DateTime.Round(DateTimeExtensions.RoundTo.Minute) == dateTime.Round(DateTimeExtensions.RoundTo.Minute));
        }

        public virtual bool RemoveExecutor()
        {
            if (HasExecutor()){
                Executor = null;
                return true;
            }
            return false;
        }

        public virtual IEnumerable<AbstractTaskHistoryEntry> GetDiffComments(Task newTask)
        {
            var oldComments =
                historyEntries.Where(x => x.TaskHistoryEntryType == HistoryEntryType.Comment);
            var newComments =
                newTask.HistoryEntries.Where(x => x.TaskHistoryEntryType == HistoryEntryType.Comment);
            return newComments.Except(oldComments);
        }

        public virtual IEnumerable<AbstractTaskHistoryEntry> GetDiffFiles(Task newTask)
        {
            var oldComments =
                historyEntries.Where(x => x.TaskHistoryEntryType == HistoryEntryType.File);
            var newComments =
                newTask.HistoryEntries.Where(x => x.TaskHistoryEntryType == HistoryEntryType.File);
            return newComments.Except(oldComments);
        }

        public virtual bool SetTag(string tag, Account logonAccount)
        {
            if (Tag != tag){
                Tag = tag;
                InvisibleSystemMessage(logonAccount, Tools.RedColorString("Тег задачи: " +
                                                                          tag));
                CleanAllReadersWithout(logonAccount);
                SetTaskInProgress();
                return true;
            }
            return false;
        }

        public virtual RoleEnum GetRole(Account account)
        {
            if (null != Executor)
                if (Executor.Equals(account)) return RoleEnum.Executor;
            if (Owner.Equals(account)) return RoleEnum.Owner;
            if (IsParticipant(account)) return RoleEnum.Participant;
            return RoleEnum.Other;
        }

        public virtual bool ChangeTaskName(string taskName, Account currentAccount)
        {
            if (TaskName != taskName){
                VisibleSystemMessage(currentAccount, Tools.RedColorString("Имя задачи изменено с " + TaskName +
                                                                          " на " + taskName));
                TaskName = taskName;
                CleanAllReadersWithout(currentAccount);
                SetTaskInProgress();
                return true;
            }
            return false;
        }

        public virtual IEnumerable<WorkingPeriod> GetAllCompleteWorkingPeriods(Account currentAccount)
        {
            return WorkingPeriods.Where(x => x.Owner == currentAccount).Where(x => x.StopTime != null);
        }

        public virtual string GetResultWorkingTime(Account account)
        {
            var y = GetAllCompleteWorkingPeriods(account).Select(x => x.StopTime - x.StartTime);
            var resultWorkingTime = new DateTime();
            resultWorkingTime = y.Aggregate(resultWorkingTime, (current, timeSpan) => current + timeSpan.Value);
            return string.Format("{0}:{1}", resultWorkingTime.Minute, resultWorkingTime.Second);
        }

        public virtual bool HasRatingToChanging(Account account, TimeSpan period)
        {
            return ratings.Any(x => DateTime.Now - x.DateCreate <= period && x.Owner.UID == account.UID && x.Task.UID == UID);
        }

        public virtual bool ExistRatingFromOwner()
        {
            return ratings.Any(x => x.Owner.UID == Owner.UID);
        }

        public virtual void ChangeRating(Account account, int value, string text, string role)
        {
            var ratinglist = ratings.Where(x => x.Owner.UID == account.UID && x.Task.UID == UID).ToList();
            foreach (var rating in ratinglist)
                ratings.Remove(rating);
            AddRating(account, value, text, role);
        }

        public virtual void AddRating(Account account, int value, string text, string role)
        {
            var rating = new Rating(this, account, value, role);
            if (!string.IsNullOrEmpty(text)) rating.SetComment(text);
            ratings.Add(rating);
        }

        public virtual int GetSummaryRating()
        {
            return ratings.Any() ? ratings.Sum(x => x.RatingValue) : 0;
        }

        public virtual Rating GetRating(Account account)
        {
            return ratings.SingleOrDefault(x => x.Owner.UID == account.UID && x.Task.UID == UID);
        }

        public virtual IEnumerable<Rating> GetRatingsForPeriod(DateTime start, DateTime end)
        {
            return ratings.Where(x => x.DateCreate >= start && x.DateCreate <= end);
        }
    }
}